package org.naxx.security.naxxlet.model.combiner;

import java.util.List;

import org.naxx.security.algorithm.PolicyCombinigAlgorithm;
import org.naxx.security.common.Decision;
import org.naxx.security.common.NotYetImplemented;
import org.naxx.security.model.Policy;
import org.naxx.security.model.request.Request;

public final class PolicyCombiner {

	private PolicyEvaluator policyEvaluator = new PolicyEvaluator();
	
	public Decision evaluate(List<Policy> policies, Request request, PolicyCombinigAlgorithm policyCombiningAlgorithm) {
		if(PolicyCombinigAlgorithm.DENY_OVERRIDES.equals(policyCombiningAlgorithm)) {
			return this.denyOverrides(policies, request);
		}
		else if(PolicyCombinigAlgorithm.ORDERED_DENY_OVERRIDES.equals(policyCombiningAlgorithm)) {
			return this.orderedDenyOverrides(policies, request);
		}
		else if(PolicyCombinigAlgorithm.PERMIT_OVERRIDES.equals(policyCombiningAlgorithm)) {
			return this.permitOverrides(policies, request);
		}
		else if(PolicyCombinigAlgorithm.ORDERED_PERMIT_OVERRIDES.equals(policyCombiningAlgorithm)) {
			return this.permitOverrides(policies, request);
		}
		else if(PolicyCombinigAlgorithm.FIRST_APPLICABLE.equals(policyCombiningAlgorithm)) {
			return this.firstApplicable(policies, request);
		}
		else if(PolicyCombinigAlgorithm.ONLY_ONE_APPLICABLE.equals(policyCombiningAlgorithm)) {
			return this.onlyOneApplicable(policies, request);
		}
		else {
			throw new IllegalStateException("All combining algorithms have not been implemented '" 
					+ policyCombiningAlgorithm.name() + "'");
		}
	}
	
	Decision orderedDenyOverrides(List<Policy> policies, Request request) {
		return this.denyOverrides(policies, request);
	}
	
	Decision denyOverrides(List<Policy> policies, Request request) {
		boolean atLeastOnePermit = false;
		for(Policy policy : policies) {
			Decision decision = this.policyEvaluator.evaluate(policy, request);
			if(Decision.DENY.equals(decision)) {
				return Decision.DENY;
			}
			if(Decision.PERMIT.equals(decision)) {
				atLeastOnePermit = true;
				continue;
			}
			if(Decision.NOT_APPLICABLE.equals(decision)) {
				continue;
			}
			if(Decision.INDETERMINATE.equals(decision)) {
				return Decision.DENY;
			}
		}
		if(atLeastOnePermit) {
			return Decision.PERMIT;
		}
		return Decision.NOT_APPLICABLE;
	}
	
	Decision permitOverrides(List<Policy> policies, Request request) {
		boolean atLeastOneError = false;
		boolean atLeastOneDeny = false;
		for(Policy policy : policies) {
			Decision decision = this.policyEvaluator.evaluate(policy, request);
			if(Decision.DENY.equals(decision)) {
				atLeastOneDeny = true;
				continue;
			}
			if(Decision.PERMIT.equals(decision)) {
				return Decision.PERMIT;
			}
			if(Decision.NOT_APPLICABLE.equals(decision)) {
				continue;
			}
			if(Decision.INDETERMINATE.equals(decision)) {
				atLeastOneError = true;
				continue;
			}
		}
		if(atLeastOneDeny) {
			return Decision.DENY;
		}
		if(atLeastOneError) {
			return Decision.INDETERMINATE;
		}
		return Decision.NOT_APPLICABLE;
	}

	Decision orderedPermitOverrides(List<Policy> policies, Request request) {
		return this.permitOverrides(policies, request);
	}
	
	Decision firstApplicable(List<Policy> policies, Request requestContext) {
		for(Policy policy : policies) {
			Decision decision = this.policyEvaluator.evaluate(policy, requestContext);
			if(Decision.DENY.equals(decision)) {
				return Decision.DENY;
			}
			if(Decision.PERMIT.equals(decision)) {
				return Decision.PERMIT;
			}
			if(Decision.NOT_APPLICABLE.equals(decision)) {
				continue;
			}
			if(Decision.INDETERMINATE.equals(decision)) {
				return Decision.INDETERMINATE;
			}
		}
		return Decision.NOT_APPLICABLE;
	}
	
	Decision onlyOneApplicable(List<Policy> policies, Request request) {
		throw new NotYetImplemented("only-one-applicale combining algorithm is not yet implemenet");
	}
}
